package com.shigen.file.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.shigen.file.common.Common.Redis;
import com.shigen.file.common.Result;
import com.shigen.file.config.ConfigProperty;
import com.shigen.file.config.TokenUtil;
import com.shigen.file.entity.LoginLog;
import com.shigen.file.entity.User;
import com.shigen.file.entity.UserInfo;
import com.shigen.file.mapper.LoginLogMapper;
import com.shigen.file.mapper.UserInfoMapper;
import com.shigen.file.mapper.UserMapper;
import com.shigen.file.req.UserRequest.Login;
import com.shigen.file.res.UserResponse.LoginResponse;
import com.shigen.file.service.IUserService;
import com.shigen.file.util.IPUtil;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author shigen.fu
 * @since 2022-06-04
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

  @Resource
  private LoginLogMapper loginLogMapper;
  @Resource
  private StringRedisTemplate stringRedisTemplate;
  @Resource
  private UserInfoMapper userInfoMapper;
  @Resource
  private ConfigProperty configProperty;
  @Resource
  private TokenUtil tokenUtil;

  @Override
  @Transactional
  public Result login(Login login, String ip, String userAgent) {
    // 登录次数错误记录key
    String failedCountKey = Redis.LOGIN_FAILED_COUNT_PREFIX + login.getId();
    // 在线信息记录key
    String onlineInfoKey = Redis.ONLINE_INFO_PREFIX + login.getId();
    // 登录失败次数
    Integer failedCount = 0;
    // 初始化登录的记录
    stringRedisTemplate.opsForValue().setIfAbsent(failedCountKey, String.valueOf(0), configProperty.getFailDuration(), TimeUnit.SECONDS);
    // 获得已经登录错误的次数
    failedCount = Integer.valueOf(stringRedisTemplate.opsForValue().get(failedCountKey));

    // 超过试错的次数
    if (failedCount >= configProperty.getAllowFailTime()) {
      Long expire = stringRedisTemplate.getExpire(failedCountKey);
      String prompt = "";
      if (expire != null && expire > 0) {
        prompt = expire / 60 > 0 ? expire / 60 + " 分钟" : expire % 60 + " 秒";
      }
      String msg = "账号曾多次登录失败，请 " + prompt + "之后重试";
      return Result.err(msg);
    } else {
      // 查库匹配信息
      User userInTable = lambdaQuery().eq(User::getId, login.getId()).eq(User::getPassword, login.getPassword()).eq(User::getIsDel, false).one();
      // 记录此次登录信息
      recordLoginLog(login, userInTable, ip, userAgent);

      // 登录失败
      if (userInTable == null) {
        failedCount++;
        stringRedisTemplate.opsForValue().set(failedCountKey, String.valueOf(failedCount), configProperty.getFailDuration(), TimeUnit.SECONDS);
        return Result.err("账号或者密码错误");
      } else {
        // 密码验证正确，确认是否在其它设备登录
        Map<Object, Object> onlineInfo = stringRedisTemplate.opsForHash().entries(onlineInfoKey);
        // 验证是否可以多台设备登录
        boolean allowMultipleDevices = Boolean.parseBoolean(configProperty.getAllowMultipleDevices());
        if (CollectionUtil.isNotEmpty(onlineInfo) && !allowMultipleDevices) {
          log.info("onlineInfo: {} allowMultipleDevices: {}", onlineInfo.toString(), false);
          return Result.err("账号在 " + onlineInfo.get("loginTime") + "  于 " + onlineInfo.get("addr") + " 已登录！");
        } else {
          // 生成token 并查表，存储用户的信息
          String token = tokenUtil.getEncryptToken();
          String tokenKey = Redis.TOKEN_PREFIX + token;
          // 查询出用户信息
          UserInfo userInfo = userInfoMapper.selectOne(new LambdaQueryWrapper<UserInfo>().eq(UserInfo::getUserId, login.getId()).eq(UserInfo::getIsDel, 0));
          // 存储用户信息
          stringRedisTemplate.opsForValue().set(tokenKey, JSONObject.toJSONString(userInfo), configProperty.getTokenTimeOut(), TimeUnit.SECONDS);
          // 存储登录信息
          stringRedisTemplate.opsForHash().putAll(onlineInfoKey, new HashMap<String, String>() {{
            put("loginTime", DateUtil.now());
            put("addr", IPUtil.getCityInfoStr(ip));
          }});
          LoginResponse loginResponse = new LoginResponse().setUserId(Long.valueOf(userInfo.getUserId())).setToken(token);
          return Result.success(loginResponse);
        }
      }
    }
  }

  /**
   * 记录登录日志
   */
  private void recordLoginLog(Login login, User userInTable, String ip, String userAgent) {
    //记录日志信息
    String relIp = IPUtil.isIp(ip) ? ip : "";
    String cityInfo = null;
    try {
      cityInfo = IPUtil.getCityInfoStr(relIp);
    } catch (Exception e) {
      log.error("通过IP {} 获得城市信息失败", relIp);
    }

    LoginLog loginLog = new LoginLog()
        .setId(login.getId())
        .setMurmur(login.getMurmur())
        .setIpAddr(ip)
        .setLocation(cityInfo);
    // 处理浏览器和操作系统
    try {
      UserAgent agent = UserAgentUtil.parse(userAgent);
      String browser = agent.getBrowser().toString();
      String os = agent.getOs().toString();
      loginLog.setBrowser(browser);
      loginLog.setOs(os);
    } catch (Exception e) {
      log.error("浏览器和操作系统获取失败，exception: {}", e.getMessage());
    }
    int status = userInTable == null ? 1 : 0;
    loginLog.setStatus(status);
    loginLog.setLoginTime(LocalDateTime.now());
    loginLogMapper.insert(loginLog);
  }
}
